package com.hmdp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import com.hmdp.mapper.UserMapper;
import com.hmdp.service.IUserService;
import com.hmdp.utils.RedisConstants;
import com.hmdp.utils.RegexUtils;
import com.hmdp.utils.SystemConstants;
import com.hmdp.utils.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;


@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 发送验证码：判断手机号是否合法，不合法返回，合法生成验证码存入redis
     *
     * @param phone
     * @param session
     * @return
     */
    @Override
    public Result sendCode(String phone, HttpSession session) {

        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号不合法");
        }
        // 生成验证码
        String code = RandomUtil.randomNumbers(6);

        // 存入session
        // session.setAttribute("code", s);

        // 存入redis
        stringRedisTemplate
                .opsForValue()
                .set(
                        RedisConstants.LOGIN_CODE_KEY + phone,
                        code,
                        RedisConstants.LOGIN_CODE_TTL,
                        TimeUnit.MINUTES);
        // 模拟发送
        log.info(code);

        return Result.ok();
    }

    /**
     * 登录接口：校验手机号，比对redis中验证码code， 查数据库：得到用户（无创建，有不创建） 存入redis， token：user 30min 返回token给前端存储，下次前端携带
     *
     * @param loginForm
     * @param session
     * @return
     */
    @SuppressWarnings("AlibabaRemoveCommentedCode")
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {

        // 校验手机号，密码
        String phone = loginForm.getPhone();

        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号不合法");
        }
        String code = loginForm.getCode();
        // 验证码从redis获取
        String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + phone);
        if (cacheCode == null || !cacheCode.equals(code)) {
            return Result.fail("验证码错误");
        }

        //        Object cacheCode = session.getAttribute("code");
        //        if (cacheCode == null || !cacheCode.toString().equals(code)) {
        //            return Result.fail("验证码错误");
        //        }

        User user = query().eq("phone", phone).one();
        if (user == null) {
            user = createUserWithPhone(phone);
        }
        // code保存到redis，令牌作为key，json作为value
        String token = UUID.randomUUID().toString(true);
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);

        Map<String, Object> map =
                BeanUtil.beanToMap(
                        userDTO,
                        new HashMap<>(3),
                        CopyOptions.create()
                                .setIgnoreNullValue(true)
                                .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));

        String tokenKey = RedisConstants.LOGIN_USER_KEY + token;

        stringRedisTemplate.opsForHash().putAll(tokenKey, map);
        // 有效期 30min
        stringRedisTemplate.expire(tokenKey, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);

        // session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));

        return Result.ok(token);
    }

    @Override
    public Result sign() {
        Long id = UserHolder.getUser().getId();
        LocalDateTime now = LocalDateTime.now();
        String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));

        String key = RedisConstants.USER_SIGN_KEY + id + keySuffix;
        // 今天是本月的第几天
        int dayOfMonth = now.getDayOfMonth() - 1;

        Boolean aBoolean = stringRedisTemplate.opsForValue().setBit(key, dayOfMonth, true);

        return Result.ok();
    }

    @Override
    public Result signCount() {
        Long id = UserHolder.getUser().getId();
        LocalDateTime now = LocalDateTime.now();
        String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));

        String key = RedisConstants.USER_SIGN_KEY + id + keySuffix;
        // 今天是本月的第几天
        int dayOfMonth = now.getDayOfMonth();

        // 获取本月签到数，返回的是十进制数
        List<Long> result = stringRedisTemplate
                .opsForValue()
                .bitField(
                        key,
                        BitFieldSubCommands
                                .create()
                                .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
                                .valueAt(0));
        if (result == null || result.isEmpty()) {
            return Result.ok(0);
        }
        Long num = result.get(0);
        if (num == null || num == 0) {
            return Result.ok(0);
        }
        int co = 0;
        while (true) {
            if ((num & 1) == 0) {
                break;
            } else {
                co++;
            }
            num >>>= 1;
        }
        // 与1与运算，bit是0，停止，1，加一
        return Result.ok(co);
    }

    @Override
    public Result logout(HttpServletRequest request) {
        String tokenKey = RedisConstants.LOGIN_USER_KEY + request.getHeader("authorization");
        stringRedisTemplate.delete(tokenKey);
        return Result.ok();
    }

    private User createUserWithPhone(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
        save(user);
        return user;
    }
}
